package no.cicero.testframework.reporting.reports;

import no.cicero.testframework.reporting.Reporting;
import no.cicero.testframework.reporting.ReportingStep;
import no.cicero.testframework.reporting.ReportingTestCase;
import no.cicero.testframework.reporting.ReportingUseCase;

import java.io.*;
import java.util.*;

public class TimeReport extends AbstractReport{
	private static class TimeReportImpl{
		private static class TimeTrackingEntry{
			long delaySum = 0;
			long reportersNum = 0;
			Map<TimeReport, Date> runningEntries = Collections.synchronizedMap(new HashMap<TimeReport, Date>());
		}

		private final Set<TimeReport> reports = Collections.synchronizedSet(new HashSet<TimeReport>());
		private final Set<TimeReport> startedReports = Collections.synchronizedSet(new HashSet<TimeReport>());
		private final Set<TimeReport> finishedReports = Collections.synchronizedSet(new HashSet<TimeReport>());
		boolean started = false;

		private final Map<String, TimeTrackingEntry> useCasesTracking = Collections.synchronizedMap(new LinkedHashMap<String, TimeTrackingEntry>());
		private final Map<String, TimeTrackingEntry> testCasesTracking = Collections.synchronizedMap(new LinkedHashMap<String, TimeTrackingEntry>());
		private final Map<String, TimeTrackingEntry> webMethodsTracking = Collections.synchronizedMap(new TreeMap<String, TimeTrackingEntry>());

		private File outputDirectory;

		synchronized public void registerReport(TimeReport report, File outputDirectory) {
			assert !started: "Reporting session already started";

			this.outputDirectory = outputDirectory;
			reports.add(report);
		}

		synchronized public void reportStarted(Reporting reporter, TimeReport report) {
			assert reports.contains(report): "Report not registered";
			assert !startedReports.contains(report): "Report already started";
			assert !finishedReports.contains(report): "Report already finished";
			started = true;

			startedReports.add(report);
		}

		synchronized public void reportFinished(TimeReport report) {
			assert startedReports.contains(report): "Report not started";
			assert !finishedReports.contains(report): "Report already finished";

			startedReports.remove(report);
			finishedReports.add(report);

			if(finishedReports.size() == reports.size()){
				try{
					writeReport();
				}catch(IOException ex){
					ex.printStackTrace();
				}finally{
					started = false;
					startedReports.clear();
					finishedReports.clear();
					useCasesTracking.clear();
					testCasesTracking.clear();
					webMethodsTracking.clear();
				}
			}
		}

		public void useCaseStarted(ReportingUseCase useCase, TimeReport report) {
			assert startedReports.contains(report): "Report not started";
			registerStart(useCasesTracking, useCase.getUseCaseName(), report);
		}

		public void useCaseEnded(ReportingUseCase useCase, TimeReport report) {
			assert startedReports.contains(report): "Report not started";
			registerEnd(useCasesTracking, useCase.getUseCaseName(), report);
		}

		public void testCaseStarted(ReportingTestCase testCase, TimeReport report) throws IOException {
			assert startedReports.contains(report): "Report not started";
			registerStart(testCasesTracking, testCase.getReporting().getCurrentUseCase().getUseCaseName() + "/" + testCase.getTestCaseName(), report);
		}

		public void testCaseEnded(ReportingTestCase testCase, TimeReport report) {
			assert startedReports.contains(report): "Report not started";
			registerEnd(testCasesTracking, testCase.getReporting().getCurrentUseCase().getUseCaseName() + "/" + testCase.getTestCaseName(), report);
		}

		public void stepStarted(ReportingStep step, TimeReport report) {
			assert startedReports.contains(report): "Report not started";
			registerStart(webMethodsTracking, step.getStep().getWebservice() + "." + step.getStep().getMethod(), report);
		}

		public void stepEnded(ReportingStep step, TimeReport report) {
			assert startedReports.contains(report): "Report not started";
			registerEnd(webMethodsTracking, step.getStep().getWebservice() + "." + step.getStep().getMethod(), report);
		}

		private static void registerStart(Map<String, TimeTrackingEntry> tracker, String id, TimeReport report){
			Date current = new Date();

			TimeTrackingEntry entry = null;
			synchronized(tracker){
				entry = tracker.get(id);
				if(entry == null){
					entry = new TimeTrackingEntry();
					tracker.put(id, entry);
				}
			}

			entry.runningEntries.put(report, current);
		}

		private static void registerEnd(Map<String, TimeTrackingEntry> tracker, String id, TimeReport report){
			Date current = new Date();

			TimeTrackingEntry entry = tracker.get(id);
			Date begin = entry.runningEntries.remove(report);

			synchronized(entry){
				++entry.reportersNum;
				entry.delaySum += current.getTime() - begin.getTime();
			}
		}

		private void writeReport() throws IOException {
			File output = new File(outputDirectory, "time_report.txt");
			PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(output)));

			writeReportPart(writer, "Use Cases", useCasesTracking);
			writeReportPart(writer, "Test Cases", testCasesTracking);
			writeReportPart(writer, "Web Methods", webMethodsTracking);

			writer.close();
		}

		private static void writeReportPart(PrintWriter writer, String partName, Map<String, TimeTrackingEntry> tracker){
			writer.println("---------------- " + partName + ": ----------------");
			for(Map.Entry<String, TimeTrackingEntry> useCaseEntry: tracker.entrySet()){
				double avg = (double)useCaseEntry.getValue().delaySum / ((double)useCaseEntry.getValue().reportersNum  * 1000d);
				writer.println(useCaseEntry.getKey() + "\t" + avg + "\t" + useCaseEntry.getValue().reportersNum);
			}
			writer.println();
		}
	}

	private static TimeReportImpl pImpl = new TimeReportImpl();

	public TimeReport(File outputDirectory){
		pImpl.registerReport(this, outputDirectory);
	}

	@Override
	public void reportStarted(Reporting reporter) throws IOException {
		pImpl.reportStarted(reporter, this);
	}

	@Override
	public void reportFinished() {
		pImpl.reportFinished(this);
	}

	@Override
	public void useCaseStarted(ReportingUseCase useCase) {
		pImpl.useCaseStarted(useCase, this);
	}

	@Override
	public void useCaseSucceeded(ReportingUseCase useCase) {
		pImpl.useCaseEnded(useCase, this);
	}

	@Override
	public void useCaseFailed(ReportingUseCase useCase, Exception ex) {
		pImpl.useCaseEnded(useCase, this);
	}

	@Override
	public void testCaseStarted(ReportingTestCase testCase) throws IOException {
		pImpl.testCaseStarted(testCase, this);
	}

	@Override
	public void testCaseFailed(ReportingTestCase testCase, Exception ex) {
		pImpl.testCaseEnded(testCase, this);
	}

	@Override
	public void testCaseSucceeded(ReportingTestCase testCase) {
		pImpl.testCaseEnded(testCase, this);
	}

	@Override
	public void stepStarted(ReportingStep step) {
		pImpl.stepStarted(step, this);
	}

	@Override
	public void stepSuccess(ReportingStep step) {
		pImpl.stepEnded(step, this);
	}

	@Override
	public void stepFailed(ReportingStep step) {
		pImpl.stepEnded(step, this);
	}

}
